package cn.yto.print.controller;

import cn.hutool.json.JSONUtil;
import cn.yto.print.config.CustomWindowEvent;
import cn.yto.print.config.ThreadPoolManager;
import cn.yto.print.config.WeightLinkedContext;
import cn.yto.print.config.i18n.LocaleChangeEvent;
import cn.yto.print.constants.AssistantConstants;
import cn.yto.print.model.response.WeightResponse;
import cn.yto.print.service.SerialPortWeightService;
import cn.yto.print.socket.WeightSocketEndpoint;
import cn.yto.print.utils.CommonUtil;
import com.fazecast.jSerialComm.SerialPort;
import de.felixroske.jfxsupport.FXMLController;
import de.felixroske.jfxsupport.GUIState;
import javafx.application.Platform;
import javafx.beans.property.SimpleStringProperty;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.control.ComboBox;
import javafx.scene.control.Label;
import javafx.scene.layout.AnchorPane;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.event.EventListener;

import javax.annotation.PreDestroy;
import java.net.URL;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.ResourceBundle;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

@Slf4j
@FXMLController
public class SerialPortController extends BaseController implements Initializable {

    private static final Map<String, String> LOCATION = new HashMap<>();

    private static ScheduledExecutorService executor;
    private final SimpleStringProperty displayWeight = new SimpleStringProperty("0");

    @FXML
    public Label serialPort;
    @FXML
    public ComboBox serialPortList;
    @FXML
    public Label baudRate;
    @FXML
    public ComboBox baudRateList;
    @FXML
    public Label weightModel;
    @FXML
    public Label weightLabel;
    @FXML
    public Label deviceConnect;
    @FXML
    public Label deviceStatus;
    @FXML
    public Label wmsConnect;
    @FXML
    public Label wmsStatus;
    @FXML
    public Label runPortTitle;
    @FXML
    public Label runPortLabel;
    @FXML
    public Label baudRateTitle;
    @FXML
    public Label runBaudRate;
    @FXML
    public Label serialStatusTitle;
    @FXML
    public Label serialPortStatus;
    @FXML
    public Label runSerialPort;
    @FXML
    public Label serialPortName;
    @FXML
    public AnchorPane maskCover;
    @FXML
    public Label coverText;

    @Value("${server.port}")
    private String serverPort;
    @Autowired
    private SerialPortWeightService serialPortService;
    @Autowired
    private WeightSocketEndpoint weightSocketEndpoint;

    private volatile long lastEventTime;

    @Override
    public void initialize(URL location, ResourceBundle resources) {

        weightModel.textProperty().bind(I18N.getStringBinding("weight.serialport.mode"));
        serialPort.textProperty().bind(I18N.getStringBinding("weight.serialport.name"));
        baudRate.textProperty().bind(I18N.getStringBinding("weight.baudrate.name"));
        deviceConnect.textProperty().bind(I18N.getStringBinding("weight.device.linked"));
        wmsConnect.textProperty().bind(I18N.getStringBinding("weight.wms.linked"));
        serialStatusTitle.textProperty().bind(I18N.getStringBinding("weight.status.title"));
        serialPortName.textProperty().bind(I18N.getStringBinding("weight.serialport.title"));
        baudRateTitle.textProperty().bind(I18N.getStringBinding("weight.baudrate.title"));
        runPortTitle.textProperty().bind(I18N.getStringBinding("weight.runport.title"));
        coverText.textProperty().bind(I18N.getStringBinding("weight.model.cover"));
        runPortLabel.setText(serverPort);
        weightLabel.textProperty().bind(displayWeight);

        SerialPort[] ports = SerialPort.getCommPorts();
        if (ports.length > 0 && serialPortList.getItems().isEmpty()) {
            for (SerialPort p : ports) {
                serialPortList.getItems().add(p.getSystemPortName());
            }
        }
        serialPortList.getSelectionModel().select(AssistantConstants.DEFAULT_SERIAL_PORT);
        serialPortList.setOnAction(e -> {
            String portName = serialPortList.getValue().toString();
            int baud = (int) baudRateList.getValue();
            runInNewThread(() -> connectAndRead(portName, baud));
        });

        if (baudRateList.getItems().isEmpty()) {
            baudRateList.getItems().addAll(1200, 2400, 4800, 9600);
        }
        baudRateList.setValue(AssistantConstants.DEFAULT_BAUD_RATE);
        baudRateList.setOnAction(e -> {
            String portName = (String) serialPortList.getValue();
            int baud = (int) baudRateList.getValue();
            runInNewThread(() -> connectAndRead(portName, baud));
        });

        Platform.runLater(() -> {
            GUIState.getStage().addEventHandler(CustomWindowEvent.SERIALPORT_SHOW, e -> {
                runInNewThread(() -> connectAndRead(
                        AssistantConstants.DEFAULT_SERIAL_PORT,
                        AssistantConstants.DEFAULT_BAUD_RATE
                ));
            });
            GUIState.getStage().addEventHandler(CustomWindowEvent.CLOSE_SHOW, e -> releaseResources());
        });
    }

    /**
     * 连接并开始读取数据
     */
    private void connectAndRead(String serialPortName, int baudRate) {
        maskCover.setVisible(true);
        releaseResources();
        Platform.runLater(() -> {
            displayWeight.set("0.00");
            updateConnectStyle(wmsStatus, AssistantConstants.DISCONNECT);
            updateConnectStyle(deviceStatus, AssistantConstants.DISCONNECT);
            serialPortStatus.setText(I18N.get("weight.connect.linking"));
        });
        LOCATION.put(serialPortStatus.getId(), "weight.connect.linking");
        checkConnectStatus();
        serialPortRead(serialPortName, baudRate);
    }

    private void runInNewThread(Runnable task) {
        new Thread(task).start();
    }

    private void checkConnectStatus() {
        log.info("启动串口监控线程，正在检测称重服务连接状态 ...");
        if (executor == null || executor.isShutdown()) {
            executor = ThreadPoolManager.getSingleScheduledExecutor();
        }
        executor.scheduleWithFixedDelay(() -> {
            updateConnectStyle(wmsStatus, WeightLinkedContext.getWmsConnectStatus());
            boolean deviceInactive = (lastEventTime == 0L) || ((System.currentTimeMillis() - lastEventTime) > 3000L);
            if (deviceInactive) {
                handleDeviceDisconnect();
            }
        }, 5, 3, TimeUnit.SECONDS);
    }

    private void handleDeviceDisconnect() {
        WeightResponse res = new WeightResponse(3, "电子秤连接断开");
        weightSocketEndpoint.massMessage(JSONUtil.toJsonStr(res));
        if (WeightLinkedContext.getDeviceConnectStatus() == 1) {
            WeightLinkedContext.setDeviceConnectStatus(AssistantConstants.DISCONNECT);
            Platform.runLater(() -> {
                displayWeight.set("0.00");
                updateConnectStyle(deviceStatus, AssistantConstants.DISCONNECT);
            });
        }
    }

    private void serialPortRead(String serialPortName, int baudRate) {
        try {
            serialPortService.readWeight(serialPortName, baudRate, (weight, deviceData) -> {
                lastEventTime = System.currentTimeMillis();
                if (!displayWeight.getValue().equals(weight)) {
                    Platform.runLater(() -> displayWeight.set(weight));
                }
                if (WeightLinkedContext.getDeviceConnectStatus() != 1) {
                    WeightLinkedContext.setDeviceConnectStatus(AssistantConstants.CONNECTED);
                    Platform.runLater(() -> {
                        updateConnectStyle(deviceStatus, AssistantConstants.CONNECTED);
                        serialPortStatus.setText(I18N.get("weight.connect.open"));
                        runSerialPort.setText(deviceData.getSerialPortName());
                        runBaudRate.setText(deviceData.getSerialBaudRate());
                    });
                    LOCATION.put(serialPortStatus.getId(), "weight.connect.open");
                }
            });
        } catch (Exception e) {
            log.info("连接串口设备并读取数据发生异常", e);
            Platform.runLater(() -> {
                serialPortStatus.setText(I18N.get("weight.connect.failed"));
                showAlert(I18N.get("weight.serialport.error"));
            });
            LOCATION.put(serialPortStatus.getId(), "weight.connect.failed");
        } finally {
            maskCover.setVisible(false);
        }
    }

    private void releaseResources() {
        CommonUtil.gracefulShutdown(executor);
        serialPortService.closeSerialPort();
        WeightLinkedContext.resetDeviceConnect();
    }

    private void updateConnectStyle(Label label, Integer status) {
        Platform.runLater(() -> {
            boolean isLinked = (status == 1);
            label.setText(isLinked ? I18N.get("weight.connect.linked") : I18N.get("weight.connect.unlink"));
            label.getStyleClass().setAll(isLinked ? "label-green" : "label-red");
        });
    }

    @EventListener
    public void updateLocaleEvent(LocaleChangeEvent event) {
        Platform.runLater(() -> {
            if (wmsStatus != null) {
                updateConnectStyle(wmsStatus, WeightLinkedContext.getWmsConnectStatus());
            }
            if (deviceStatus != null) {
                updateConnectStyle(deviceStatus, WeightLinkedContext.getDeviceConnectStatus());
            }
            LOCATION.forEach((k, v) -> serialPortStatus.setText(I18N.get(v)));
        });
    }

}


